using System;
using System.Collections.Generic;
using System.Threading;
using Coditate.Common.Util;
using Coditate.Savant.TestSupport;
using Coditate.TestSupport;
using NUnit.Framework;
using Rhino.Mocks;
using Rhino.Mocks.Constraints;

namespace Coditate.Savant.Async
{
    [TestFixture]
    public class AsyncExtensionsTest
    {
        private ISimpleSavant savant;
        private ISimpleSavant2 savant2;

        [SetUp]
        public void SetUp()
        {
            savant = MockRepository.GenerateMock<ISimpleSavant>();
            savant2 = MockRepository.GenerateMock<ISimpleSavant2>();
        }

        [TearDown]
        public void TearDown()
        {
            savant.VerifyAllExpectations();
            savant2.VerifyAllExpectations();
        }

        [Test]
        public void ExecuteAsync_Get()
        {
            var a = new A2();

            savant.Expect(s => s.Get<A2>(a.ItemName)).Constraints(Is.Equal(a.ItemName)).Return(a);

            AsyncFunction<A2> function = savant.ExecuteAsync(s => s.Get<A2>(a.ItemName));

            Assert.AreSame(a, function.EndInvoke());
        }

        [Test]
        public void ExecuteAsync_Put()
        {
            var aList = new[] {new A2()};

            savant.Expect(s => s.Put(aList)).Constraints(Is.Equal(aList));

            AsyncAction action = savant.ExecuteAsync(s => s.Put(aList));

            action.EndInvoke();
        }

        [Test]
        public void ExecuteAsync_MultiPut()
        {
            var a = new A2();
            int callCount = 3;

            Action<MethodInvocation> delayAction = delegate
                {
                    Thread.Sleep(
                        100);
                };
            savant.Expect(s => s.Put(a)).IgnoreArguments().Repeat.Times(callCount).WhenCalled(delayAction);

            ActionTracker actions = null;
            for (int k = 0; k < callCount; k++)
            {
                savant.ExecuteAsync(s => s.Put(a), ref actions);
            }

            Assert.IsFalse(actions.AreCompleted);

            actions.EndInvocations();

            Assert.IsTrue(actions.AreCompleted);
        }

        [Test]
        public void ExecuteAsync_Select()
        {
            var a = new A2();
            string commandText = "select x from A2";

            savant.Expect(s => s.Select<A2>(commandText)).Constraints(Is.Equal(commandText),
                                                                      new Anything()).Return(new List<A2> {a});

            AsyncFunction<List<A2>> function = savant.ExecuteAsync(s => s.Select<A2>(commandText));

            List<A2> aList = function.EndInvoke();

            Assert.Contains(a, aList);
        }

        [Test, ExpectedException(typeof (SavantDataException), ExpectedMessage = "Simulated data error")]
        public void ExecuteAsync_Error()
        {
            var a = new A2();

            savant.Expect(s => s.Get<A2>(a.ItemName)).Constraints(Is.Equal(a.ItemName)).Throw(
                new SavantDataException("Simulated data error"));

            AsyncFunction<A2> function = savant.ExecuteAsync(s => s.Get<A2>(a.ItemName));

            function.EndInvoke();
        }

        [Test]
        public void BeginEndPut()
        {
            var aList = new[] {new A2()};

            savant.Expect(s => s.Put(aList)).Constraints(Is.Same(aList));

            IAsyncResult result = savant.BeginPut(aList, null, null);
            savant.EndPut(result);
        }

        [Test]
        public void BeginEndGet()
        {
            var a = new A2();

            savant.Expect(s => s.Get<A2>(Arg<object>.Is.Equal(a.ItemName))).Return(a);

            IAsyncResult result = savant.BeginGet<A2>(a.ItemName, null, null);
            var a2 = savant.EndGet<A2>(result);

            Assert.AreSame(a, a2);
        }

        /// <summary>
        /// Since all of the async methods are driven through the same logic this test only provides
        /// minimal code execution of the functions that aren't covered by the meatier tests.
        /// </summary>
        [Test]
        public void BeginEnd_Minimal()
        {
            // GetAttributesT
            savant.Expect(s => s.GetAttributes<A2>(null)).IgnoreArguments().Return(null);
            IAsyncResult result = savant.BeginGetAttributes<A2>(null, null, null, null);
            savant.EndGetAttributes(result);

            // Delete
            savant.Expect(s => s.Delete<A2>(null)).IgnoreArguments();
            result = savant.BeginDelete<A2>(null, null, null);
            savant.EndDelete(result);

            // DeleteAttributes
            savant2.Expect(s => s.DeleteAttributes(null, null)).IgnoreArguments();
            result = savant2.BeginDeleteAttributes(null, null, null, null, null);
            savant2.EndDeleteAttributes(result);

            // DeleteAttributesT
            savant.Expect(s => s.DeleteAttributes<A2>(null)).IgnoreArguments();
            result = savant.BeginDeleteAttributes<A2>(null, null, null, null);
            savant.EndDeleteAttributes(result);

            // Find
            savant.Expect(s => s.Find<A2>(null, 0, 0, null)).IgnoreArguments().Return(null);
            result = savant.BeginFind<A2>(null, 0, 0, null, null, null);
            savant.EndFind<A2>(result);

            // FindAttributes
            savant.Expect(s => s.FindAttributes<A2>(null, 0, 0, null)).IgnoreArguments().Return(null);
            result = savant.BeginFindAttributes<A2>(null, 0, 0, null, null, null, null);
            savant.EndFindAttributes(result);

            // Select
            savant.Expect(s => s.Select((SelectCommand<A2>) null)).IgnoreArguments().Return(null);
            result = savant.BeginSelect<A2>(null, null, null);
            savant.EndSelect<A2>(result);

            // SelectAttributesT
            savant.Expect(s => s.SelectAttributes((SelectCommand<A2>) null)).IgnoreArguments().Return(null);
            result = savant.BeginSelectAttributes<A2>(null, null, null);
            savant.EndSelectAttributes(result);

            // SelectAttributes
            savant2.Expect(s => s.SelectAttributes(null)).IgnoreArguments().Return(null);
            result = savant2.BeginSelectAttributes(null, null, null);
            savant2.EndSelectAttributes(result);

            // SelectScalar
            savant2.Expect(s => s.SelectScalar(null)).IgnoreArguments().Return(null);
            result = savant2.BeginSelectScalar(null, null, null);
            savant2.EndSelectScalar(result);

            // SelectScalarT
            savant.Expect(s => s.SelectScalar<A2>(null, null)).IgnoreArguments().Return(null);
            result = savant.BeginSelectScalar<A2>(null, null, null, null);
            savant.EndSelectScalar(result);

            // PutAttributes
            savant.Expect(s => s.PutAttributes<A2>(null)).IgnoreArguments();
            result = savant.BeginPutAttributes<A2>(null, null, null);
            savant.EndPutAttributes(result);
        }

        [Test, ExpectedException(typeof (InvalidOperationException), ExpectedMessage =
            "The provided IAsyncResult was returned from a Begin method that does not match the current End method.")]
        public void BeginEndFunction_Mismatch()
        {
            IAsyncResult result = savant.BeginPut(null, null, null);
            savant.EndGet<A2>(result);
        }

        [Test, ExpectedException(typeof (InvalidOperationException), ExpectedMessage =
            "The provided IAsyncResult was returned from a Begin method that does not match the current End method.")]
        public void BeginEndAction_Mismatch()
        {
            IAsyncResult result = savant.BeginGet<A2>(null, null, null);
            savant.EndPut(result);
        }

        [Test]
        public void BeginEndPutAttributes()
        {
            var a = new A2();
            ItemMapping mapping = ItemMapping.Create(typeof (A2));
            var valuesList = new[] {PropertyValues.CreateValues(a)};

            savant2.Expect(s => s.PutAttributes(mapping, valuesList)).Constraints(Is.Equal(mapping),
                                                                                  Is.Equal(valuesList));

            IAsyncResult result = savant2.BeginPutAttributes(mapping, valuesList, null, null);
            savant2.EndPutAttributes(result);
        }

        /// <summary>
        /// Test an async method with callback and state.
        /// </summary>
        [Test]
        public void BeginEndPut_Callback()
        {
            var a = new A2();

            savant.Expect(s => s.Put(a)).IgnoreArguments();

            string state1 = RandomData.AlphaNumericString(10, false);
            string state2 = null;
            AsyncCallback callback = delegate(IAsyncResult ar)
                {
                    state2 = (string) ar.AsyncState;
                    savant.EndPut(ar);
                };

            savant.BeginPut(new object[] {a}, callback, state1);

            WaitUtils.Default.WaitTillTrue(delegate { return state2 != null; });

            Assert.AreEqual(state1, state2);
        }

        [Test]
        public void BeginEndGet_Callback()
        {
            var a = new A2();

            savant.Expect(s => s.Get<A2>(a.ItemName)).IgnoreArguments().Return(a);

            string state1 = RandomData.AlphaNumericString(10, false);
            string state2 = null;
            AsyncCallback callback = delegate(IAsyncResult ar)
                {
                    state2 = (string) ar.AsyncState;
                    var a2 = savant.EndGet<A2>(ar);

                    Assert.AreSame(a, a2);
                };

            savant.BeginGet<A2>(a.ItemName, callback, state1);

            WaitUtils.Default.WaitTillTrue(delegate { return state2 != null; });

            Assert.AreEqual(state1, state2);
        }
    }
}